#include "ShipObject.hpp"

// Constructor
ShipObject::ShipObject()
{
	m_pShipSceneNode = 0;
	m_pShipEntityModel = 0;

	m_pPhysWorld = 0;
	m_pShipBody = 0;
	m_pShipCol = 0;

	m_pShipInputSource = 0;
	m_pShipTemplate = 0;
}

// Creates a new ship object.
void ShipObject::spawnNewShip(Ogre::SceneManager* i_pSceneMgr, std::string i_sShipName, ShipTemplate* i_pShipTemplate,
							   OgreNewt::World* i_pPhysWorld, ShipInput* i_pShipInputSource, Ogre::Vector3 i_StartPosition, Ogre::Quaternion i_StartOrientation)
{
	// Associate the correct NewtonWorld.
	m_pPhysWorld = i_pPhysWorld;

	// First create the Entity/SceneNode combination.
	m_pShipEntityModel = i_pSceneMgr->createEntity(i_pShipTemplate->sMeshName);
	m_pShipSceneNode = i_pSceneMgr->getRootSceneNode()->createChildSceneNode(i_sShipName);
	m_pShipSceneNode->attachObject(m_pShipEntityModel);

	// Set the starting position and orientation.
	m_pShipSceneNode->setPosition(i_StartPosition);
	m_pShipSceneNode->setOrientation(i_StartOrientation);

	// Set the correct input source.
	setShipInputSource(i_pShipInputSource);

	// Save the template for this ship.
	m_pShipTemplate = i_pShipTemplate;

	// Use the ship model skeleton to populate the Hardpoints on this ship.
	// TODO: Write the thing that goes here.

	// =======================
	// BILLBOARD PROTOTYPE
	// =======================

	// Create the billboard set. Like any entity it must be attached to a node.
	m_pBillboardSet = i_pSceneMgr->createBillboardSet(Ogre::String(i_sShipName + "Lights"));
	m_pShipSceneNode->attachObject(m_pBillboardSet);

	// One unintuitive thing to note is that a billboard set can only have one 
	// material assigned to it. Think of how particle emitters can only emit particles
	// of one texture. This is why that's true.
	m_pBillboardSet->setMaterialName("BasicFlare");
	m_pBillboardSet->setDefaultDimensions(5, 5);

	// Create the billboard on the "HpHeadlight" bone if it has one.
	Ogre::Vector3 LightPosition(0, 0, 0);
	if (m_pShipEntityModel->hasSkeleton())
		if (m_pShipEntityModel->getSkeleton()->hasBone("HpHeadlight"))
			LightPosition = m_pShipEntityModel->getSkeleton()->getBone("HpHeadlight")->getPosition();

	// Create a single billboard. 
	m_pWhiteLight = m_pBillboardSet->createBillboard(LightPosition, Ogre::ColourValue(1.0f, 1.0f, 1.0f, 1.0f));

	// =======================
	// BILLBOARD PROTOTYPE END
	// =======================

	// EVERYTHING BELOW THIS POINT IS NEWTON PHYSICS SETUP

	// Just like Ogre, Newton uses two objects per "thing." In Newton's case,
	// it is a Collision and a Body. The Collision is analogous to the Entity,
	// and the Body is analogous to the SceneNode. You create Collisions using
	// the entity, and SceneNodes are attached to Bodies. Also just like 
	// SceneNodes, bodies must have an associated collision in order for them 
	// to do anything.

	// Create the Collision object.
	Ogre::Vector3 ShipSize = m_pShipEntityModel->getBoundingBox().getSize();
	m_pShipCol = new OgreNewt::CollisionPrimitives::ConvexHull(m_pPhysWorld, m_pShipEntityModel, 0);
	OgreNewt::CollisionPtr CollisionPtr(m_pShipCol);

	//Create the Body object using the collision object created above.
	m_pShipBody = new OgreNewt::Body(m_pPhysWorld, CollisionPtr);
	m_pShipBody->setPositionOrientation(i_StartPosition, i_StartOrientation);

	// Associate the ship SceneNode with this Body so that Newton will update
	// the position of this body automatically. 
	m_pShipBody->attachNode(m_pShipSceneNode);

	// Set the mass and inertia of the ship.
	Ogre::Real MassValue = i_pShipTemplate->fMass;
	Ogre::Vector3 CenterOfMass, Inertia;
	m_pShipCol->calculateInertialMatrix(Inertia, CenterOfMass);
	Inertia *= MassValue;
	m_pShipBody->setMassMatrix(MassValue, Inertia);

	// Give the ship rotational/linear drag for simplicities sake. This may be 
	// taken out depending on how the ship physics are going to work.
	m_pShipBody->setLinearDamping(i_pShipTemplate->fLinearDrag);
	m_pShipBody->setAngularDamping(i_pShipTemplate->fAngularDrag);

	// Set the force callback for this ship.
	m_pShipBody->setCustomForceAndTorqueCallback<ShipObject>(&ShipObject::applyShipForces, this);

	// Turn off auto-sleep.
	m_pShipBody->setAutoSleep(false);

	// Set the user data, this is so that when the body is called in the callback,
	// it will be easily able to keep track of what the ship in question is.
	m_pShipBody->setUserData(this);

}

// What to do on every update cycle.
void ShipObject::updateShip(double timeSinceLastFrame)
{
	// Ever since the physics engine, there's nothing to add here as of yet.
}

// This is the force callback as used by the phsysics engine. It's used to apply
// forces to the ship. It gets called automatically by the physics engine. A
// MASSIVE limitation with this approach is that this is in no way associated with
// this ShipObject. Unfortunately, this means that a pointer to some data structure
// must be passed around with the physics object that contains all of this ships
// essential information.
// Frame indepedence is automatically handled as well.
void ShipObject::applyShipForces(OgreNewt::Body* ShipBody, float timeSinceLastFrame, int nThreadIndex)
{
	// First extract a pointer to what the associated ShipInput. The Body can store
	// a small bit of user data, which in this case will be a pointer to the ship.
	ShipObject* pShipObject = static_cast<ShipObject*>(ShipBody->getUserData());	

	// Save the pointers for the stuff we want.
	ShipInput* pShipInput = pShipObject->getShipInputSource();
	ShipTemplate* pShipTemplate = pShipObject->getShipTemplate();

	// Before doing anything, first make sure that there is some sort of input
	// to even be considered.
	if (pShipInput != NULL)
	{
		// ==============================================
		// LINEAR MOTION AND THRUST
		// ==============================================

		const float SHIP_THRUST = pShipTemplate->fForwardThrust;
		Ogre::Vector3 CenterOfMass = ShipBody->getCenterOfMass();

		Ogre::Vector3 ForceToApply;

		// Move the ship forwards when the input says it should.
		if (pShipInput->m_nThrottleDirection == THROTTLE_INCREASE)
		{
			ForceToApply = Ogre::Vector3(0, 0, -SHIP_THRUST*timeSinceLastFrame);
			ShipBody->addLocalForce(ForceToApply, CenterOfMass);
		}

		// Move the ship backwards when the input says it should.
		else if (pShipInput->m_nThrottleDirection == THROTTLE_DECREASE)
		{
			ForceToApply = Ogre::Vector3(0, 0, SHIP_THRUST*timeSinceLastFrame/2);
			ShipBody->addLocalForce(ForceToApply, CenterOfMass);
		}

		// ==============================================
		// ROTATIONAL MOTION AND THRUST
		// ==============================================
		const float SHIP_ROTATE_THRUST_X = pShipTemplate->fSteeringThrust.x/2;
		const float SHIP_ROTATE_THRUST_Y = pShipTemplate->fSteeringThrust.y/2;
		const float SHIP_ROTATE_THRUST_Z = pShipTemplate->fSteeringThrust.z/2;

		// Create location vectors of in front of, and behind the ship. The
		// "thrusters" both fire at the same time, in opposite directions so
		// as to make the ship spin on its axis.
		Ogre::Vector3 TurnPushLocation = CenterOfMass;
		TurnPushLocation.z += 10.0f;

		// ==============
		// PITCH COMMANDS
		// ==============
		// Yaw the ship left when the input says it should.
		if (pShipInput->m_nPitchDirection == PITCH_UP)
		{
			ForceToApply = Ogre::Vector3(0, -SHIP_ROTATE_THRUST_Y, 0);
			
			ShipBody->addLocalForce(ForceToApply, TurnPushLocation);
			ShipBody->addLocalForce(-ForceToApply, -TurnPushLocation);
		}
		// Yaw the ship right when the input says it should.
		else if (pShipInput->m_nPitchDirection == PITCH_DOWN)
		{
			ForceToApply = Ogre::Vector3(0, SHIP_ROTATE_THRUST_Y, 0);
			ShipBody->addLocalForce(ForceToApply, TurnPushLocation);
			ShipBody->addLocalForce(-ForceToApply, -TurnPushLocation);
		}

		// ==============
		// YAW COMMANDS
		// ==============
		// Yaw the ship left when the input says it should.
		if (pShipInput->m_nTurnDirection == TURN_LEFT)
		{
			ForceToApply = Ogre::Vector3(SHIP_ROTATE_THRUST_X, 0, 0);
			ShipBody->addLocalForce(ForceToApply, TurnPushLocation);
			ShipBody->addLocalForce(-ForceToApply, -TurnPushLocation);
		}
		// Yaw the ship right when the input says it should.
		else if (pShipInput->m_nTurnDirection == TURN_RIGHT)
		{
			ForceToApply = Ogre::Vector3(-SHIP_ROTATE_THRUST_X, 0, 0);
			ShipBody->addLocalForce(ForceToApply, TurnPushLocation);
			ShipBody->addLocalForce(-ForceToApply, -TurnPushLocation);
		}

		// ==============
		// ROLL COMMANDS
		// ==============
		TurnPushLocation = CenterOfMass;
		TurnPushLocation.y += 20.0f;

		// Yaw the ship left when the input says it should.
		if (pShipInput->m_nRollDirection == ROLL_LEFT)
		{
			ForceToApply = Ogre::Vector3(-SHIP_ROTATE_THRUST_Z, 0, 0);
			ShipBody->addLocalForce(ForceToApply, TurnPushLocation);
			ShipBody->addLocalForce(-ForceToApply, -TurnPushLocation);
		}
		// Yaw the ship right when the input says it should.
		else if (pShipInput->m_nRollDirection== ROLL_RIGHT)
		{
			ForceToApply = Ogre::Vector3(SHIP_ROTATE_THRUST_Z, 0, 0);
			ShipBody->addLocalForce(ForceToApply, TurnPushLocation);
			ShipBody->addLocalForce(-ForceToApply, -TurnPushLocation);
		}

	}
}

// Set the source of the input for this ship.
void ShipObject::setShipInputSource(ShipInput* i_pShipInputSource)
{
	m_pShipInputSource = i_pShipInputSource;
}

// Return the SceneNode that this ship uses.
Ogre::SceneNode* ShipObject::getShipSceneNode()
{
	return m_pShipSceneNode;
}

// Return the name of the scene node, which doubles as the ship name.
std::string ShipObject::getShipName()
{
	return m_pShipSceneNode->getName();
}

// Returns a pointer to this ship.
ShipObject* ShipObject::getShipPointer()
{
	return this;
}

// Returns a pointer to this ships input source.
ShipInput* ShipObject::getShipInputSource()
{
	return m_pShipInputSource;
}

// Returns a pointer to the template used to create this ship.
ShipTemplate* ShipObject::getShipTemplate()
{
	return m_pShipTemplate;
}

// Destructor
ShipObject::~ShipObject()
{
}